﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Windows.Forms;
using System.Windows.Threading;

using Perseus;
using Perseus.Data;
using Perseus.Plugins;

using Hetymine.Executioner.Data;
/**
 * TODO:
 * 
 * - IProgress implimentation
 * - Configurable context menu since each item is a command anyways.
 * - IDisplay implementation wich would show notification baloons.
 **/
namespace Hetymine.Executioner.Plugins.General.NotificationIcon {
    public class NotificationIcon : Plugin, ILoader, IConfigurable<IniFile> {
        private NotificationIconConfigurator _Configurator;

        private NotifyIcon _NotifyIcon;
        private ContextMenu _NotifyIconMenu;
        private DispatcherTimer _NotifyIconClickTimer;
        private Dictionary<string, List<MenuItem>> _ContextMenuItems;
        private EventHandler _LoadedEvent;

        public NotificationIcon() {
            this.PluginInfo = PluginInfo.FromAssembly(
                "Notification Icon",
                this.GetType().Assembly,
                "Blake Hancock",
                "http://hetymine.com/executioner/plugins/general/notificationicon/"
            );

            this._LoadedEvent = new EventHandler(Executioner_Loaded);
        }

        #region Executioner Events
        private void Executioner_Loaded(object sender, EventArgs e) {
            this.InitializeNotificationArea();

            this._NotifyIconClickTimer = new DispatcherTimer();
            this._NotifyIconClickTimer.Interval = TimeSpan.FromMilliseconds(100);
            this._NotifyIconClickTimer.IsEnabled = false;
            this._NotifyIconClickTimer.Tick += new EventHandler(NotifyIconClickTimer_Tick);
        }
        #endregion

        public void AddMenuItem(string group, MenuItem item) {
            if (this._ContextMenuItems == null) {
                Executioner.Sounds.Error();
                return;
            }

            if (!this._ContextMenuItems.ContainsKey(group)) {
                this._ContextMenuItems.Add(group, new List<MenuItem>());
            }

            this._ContextMenuItems[group].Add(item);
        }
        public bool RemoveMenuItem(MenuItem item) {
            if (this._ContextMenuItems == null) {
                Executioner.Sounds.Error();
                return false;
            }

            foreach (List<MenuItem> group in this._ContextMenuItems.Values) {
                foreach (MenuItem m in group) {
                    if (m.Equals(item)) {
                        bool result = group.Remove(m);
                        InitializeNotificationArea();
                        return result;
                    }
                }
            }

            return false;
        }

        private void InitializeNotificationArea() {
            if (this._NotifyIcon != null) {
                this._NotifyIcon.Dispose();                
            }

            // Setup tray icon and context menu
            // Uses System.Windows.Forms
            this._NotifyIcon = new System.Windows.Forms.NotifyIcon();
            try {
                this._NotifyIcon.Icon = new System.Drawing.Icon(
                    App.GetResourceStream(
                        new Uri("pack://application:,,,/Resources/executioner.ico")
                    ).Stream, 16, 16
                );
            }
            catch {
                App.Log("Error loading executioner.ico resource.");
            }
            this._NotifyIcon.Text = "Executioner";
            
            // Windows 7+ defaults to taskbar mode because of pinning and context menu options
            this._NotifyIcon.Visible = true;
            this._NotifyIcon.MouseClick += new System.Windows.Forms.MouseEventHandler(NotifyIcon_Click);
            this._NotifyIcon.DoubleClick += new EventHandler(NotifyIcon_DoubleClick);

            this._NotifyIconMenu = new System.Windows.Forms.ContextMenu();

            string[] menuItems = Executioner.Config.GetString(
                "Notification Icon", 
                "Context Menu Items", 
                NotificationIcon.DefaultContextMenuItems
            ).Split(
                "\n", 
                SplitOptions.Trim | 
                SplitOptions.RemoveEmptyEntries
            );

            // We dont want first item to be a separator so we start off as true
            bool lastIsSep = true;

            foreach (string item in menuItems) {
                if (item.CharAt(0) == "-") {
                    if (lastIsSep) {
                        continue;
                    }
                    this._NotifyIconMenu.MenuItems.Add("-");
                    lastIsSep = true;
                }

                // TODO: Make it so you can specify specific groups and add only them
                if (item == "{0}") {
                    foreach (List<MenuItem> group in this._ContextMenuItems.Values) {                        
                        if (group.Count > 0) {
                            if (!lastIsSep) {
                                this._NotifyIconMenu.MenuItems.Add("-");                                
                            }
                            lastIsSep = false;
                            foreach (MenuItem m in group) {
                                this._NotifyIconMenu.MenuItems.Add(m);
                            }
                        }
                    }
                    continue;
                }

                lastIsSep = false;

                int pos = item.IndexOf(":");
                if (pos == -1) {
                    App.Log("Notification Icon: Invliad context menu item '" + item + "'.");
                    continue;
                }

                string name = item.Substring(0, pos).Trim();
                string command = item.Substring(pos + 1).Trim();
                MenuItem menuItem = new MenuItem(name, delegate(object sender, EventArgs e) {
                    Executioner.ExecuteCommand("@execute: " + command, string.Empty, false);
                });
                this._NotifyIconMenu.MenuItems.Add(menuItem);
            }

            // Remove last item if separator
            if (lastIsSep) {
                this._NotifyIconMenu.MenuItems.RemoveAt(this._NotifyIconMenu.MenuItems.Count - 1);
            }

            if (this._NotifyIconMenu.MenuItems.Count > 0) {
                this._NotifyIcon.ContextMenu = this._NotifyIconMenu;
            }
        }
        public static string DefaultContextMenuItems {
            get {
                return "Reload Commands: -reload" + Environment.NewLine +
                    "Configuration: -config" + Environment.NewLine +                    
                    "{0}" + Environment.NewLine +
                    "-" + Environment.NewLine +
                    "Website: -website" + Environment.NewLine +
                    "-" + Environment.NewLine +
                    "Exit: -exit";
            }
        }
        private void NotifyIconClickBehaviour(int clickBehaviour) {
            switch (clickBehaviour) {
                case 1:
                    System.Reflection.MethodInfo mi = this._NotifyIcon.GetType().GetMethod(
                        "ShowContextMenu",
                        BindingFlags.Instance | BindingFlags.NonPublic
                    );
                    mi.Invoke(this._NotifyIcon, null);
                    break;
                case 2:
                    Executioner.ExecuteCommand("@execute", "-config", false);
                    break;
                case 3:
                    Executioner.ExecuteCommand("@execute", "-reload", false);
                    break;
            }
        }        

        #region Notify Icon Events
        private void NotifyIcon_Click(object sender, System.Windows.Forms.MouseEventArgs e) {
            if (e.Button == System.Windows.Forms.MouseButtons.Left) {
                this._NotifyIconClickTimer.IsEnabled = true;
            }
        }
        private void NotifyIcon_DoubleClick(object sender, EventArgs e) {
            this._NotifyIconClickTimer.IsEnabled = false;
            int clickBehaviour = Executioner.Config.GetInt("Notification Icon", "Double Click", 2);
            if (clickBehaviour == clickBehaviour.MaxMin(1, 3)) {
                this.NotifyIconClickBehaviour(clickBehaviour);
            }
            else if (clickBehaviour == 4) {
                string command = Executioner.Config.GetString("Notification Icon", "Double Click Command", string.Empty);
                if (!command.IsEmpty()) {
                    Executioner.ExecuteCommand("@execute", command, false);
                }
            }
        }        
        private void NotifyIconClickTimer_Tick(object sender, EventArgs e) {
            this._NotifyIconClickTimer.IsEnabled = false;
            int clickBehaviour = Executioner.Config.GetInt("Notification Icon", "Single Click", 1);
            if (clickBehaviour == clickBehaviour.MaxMin(1, 3)) {
                this.NotifyIconClickBehaviour(clickBehaviour);
            }
            else if (clickBehaviour == 4) {
                string command = Executioner.Config.GetString("Notification Icon", "Single Click Command", string.Empty);
                if (!command.IsEmpty()) {
                    Executioner.ExecuteCommand("@execute", command, false);
                }
            }
        }
        #endregion

        #region ILoader and Loaded Event
        public void Load() {
            Executioner.Loaded += this._LoadedEvent;
            this._ContextMenuItems = new Dictionary<string, List<MenuItem>>();            
        }
        public void Unload() {
            this._ContextMenuItems = null;
            
            Executioner.Loaded -= this._LoadedEvent;
            
            if (this._NotifyIcon != null) {
                this._NotifyIcon.Dispose();
                this._NotifyIcon = null;
            }
        }
        #endregion
        #region IConfigurable<IniFile>
        public IConfigurator<IniFile> Configurator {
            get {
                if (this._Configurator == null) {
                    this._Configurator = new NotificationIconConfigurator();
                }

                return this._Configurator;
            }
        }
        #endregion
    }
}
